package com.ctim.web.config.shiro;

import com.ctim.model.user.Resource;
import com.ctim.model.user.Role;
import com.ctim.model.user.User;
import com.ctim.service.user.ResourceService;
import com.ctim.service.user.RoleService;
import com.ctim.service.user.UserService;
import com.ctim.web.util.EncryptUtil;
import com.ctim.web.util.HttpTools;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.web.subject.WebSubject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpRequest;

import javax.servlet.http.HttpServletRequest;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;


public class MyShiroRealm extends AuthorizingRealm {

    @Autowired
    private ResourceService resourceService;

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    /**
     * 认证信息.(身份验证)
     * :
     * Authentication 是用来验证用户身份
     *
     * @param token 令牌
     * @return AuthenticationInfo AuthenticationInfo
     * @throws AuthenticationException AuthenticationInfo
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取用户的输入的账号.
        String username = (String) token.getPrincipal();
        User user = userService.getUser(username);

        //通过username从数据库中查找 User对象，如果找到，没找到.
        //实际项目中，这里可以根据实际情况做缓存，如果不做，Shiro自己也是有时间间隔机制，2分钟内不会重复执行该方法
        if (user == null) {// 账号不存在
            return null;
        }


        user.setSalt(EncryptUtil.getDefaultSalt());
        // 获取此人所有资源
        Map<String, List<Resource>> resourceMap = resourceService.getUserResources(user.getUserId());
        user.setMenuTree(resourceMap.get("menuTree"));
        user.setPermissions(resourceMap.get("permissions"));

        //设置用户首页地址   如果有客户查询的菜单权限则显示客户列表，否则选择该用户所拥有的其中一个菜单
        for (Resource resource : user.getMenuTree()) {
            if (resource.getChildren() != null) {
                for (Resource child : resource.getChildren()) {
                    if (StringUtils.isNotBlank(child.getUrl()) && child.getUrl().equals("html/customer/customer.html")) {
                        user.setDefaultIndexUrl("html/customer/customer.html");
                        break;
                    }
                }
            } else {
                if (StringUtils.isNotBlank(resource.getUrl()) && resource.getUrl().equals("html/customer/customer.html")) {
                    user.setDefaultIndexUrl("html/customer/customer.html");
                    break;
                }
            }
        }
        if (user.getDefaultIndexUrl() == null) {
            for (Resource resource : user.getMenuTree()) {
                if (resource.getChildren() != null) {
                    for (Resource child : resource.getChildren()) {
                        if (StringUtils.isNotBlank(child.getUrl())) {
                            user.setDefaultIndexUrl(child.getUrl());
                            break;
                        }
                    }
                } else {
                    user.setDefaultIndexUrl(resource.getUrl());
                    break;
                }
            }
        }


        //获取此人所有角色
        user.setRoles(roleService.selectRoles(user.getUserId()));
        //加密方式;
        //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配，如果觉得人家的不好可以自定义实现
        return new SimpleAuthenticationInfo(
                user,//user,  用户名
                user.getPassword(), // 密码
                ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt 若明文可不填
                getName()  //realm name
        );
    }


    /**
     * 此方法调用  hasRole,hasPermission的时候才会进行回调.
     * <p>
     * 权限信息.(授权):
     * 1、如果用户正常退出，缓存自动清空；
     * 2、如果用户非正常退出，缓存自动清空；
     * 3、如果我们修改了用户的权限，而用户不退出系统，修改的权限无法立即生效。
     * （需要手动编程进行实现；放在service进行调用）
     * 在权限修改后调用realm中的方法，realm已经由spring管理，所以从spring中获取realm实例，
     * 调用clearCached方法；
     * :Authorization 是授权访问控制，用于对用户进行的操作授权，证明该用户是否允许进行当前操作，如访问某个链接，某个资源文件等。
     *
     * @param principals principals
     * @return AuthorizationInfo
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
       /*
        * 当没有使用缓存的时候，不断刷新页面的话，这个代码会不断执行，
        * 当其实没有必要每次都重新设置权限信息，所以我们需要放到缓存中进行管理；
        * 当放到缓存中时，这样的话，doGetAuthorizationInfo就只会执行一次了，
        * 缓存过期之后会再次执行。
        */
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        User user = (User) principals.getPrimaryPrincipal();
        //实际项目中，这里可以根据实际情况做缓存，如果不做，Shiro自己也是有时间间隔机制，2分钟内不会重复执行该方法
        authorizationInfo.setRoles(getRolePermissions(user.getMenuTree()));

        //添加权限
        user.getPermissions().forEach(resource -> authorizationInfo.addStringPermission(resource.getPermission()));

        //设置权限信息.
//     authorizationInfo.setStringPermissions(getStringPermissions(user.getPermissions()));

        return authorizationInfo;
    }


    /**
     * 将权限对象中的权限code取出.
     * @param resources
     * @return
     */
  public Set<String> getRolePermissions(List<Resource> resources){
      Set<String> stringPermissions = new HashSet<String>();
      for (Resource resource : resources) {
          if (resource.getChildren() != null) {
              for (Resource child : resource.getChildren()) {
                  if (StringUtils.isNotBlank(child.getPermission())) {
                      stringPermissions.add(child.getPermission());
                  }
                      break;
                  }
              }
          }
       return stringPermissions;
  }
}